import { Identifier, LetStatement, ReturnStatement, Program, IntegerLiteral, PrefixExpression, ExpressionStatement, InfixExpression, BooleanExpression, IfExpression, BlockStatement, FunctionLiteral, CallExpression, StringLiteral, ArrayLiteral, HashLiteral } from './model'
import { TokenType, TokenPriority } from './constant'
import { isNotNull, isNull } from '../utils'
import { IndexExpression } from './model/exprssion/indexExpression'


class MonkeyParser {

    constructor(lexer) {
        this.tokens = lexer.lexing()
        this.tokenPos = 0
        this.curToken = null
        this.peekToken = null
        this.nextToken()
        this.nextToken()
        this.program = new Program()

        // 前序解析表
        this.prefixParseFns = {
            [TokenType.IDENTIFIER]: this.parseIdentifier,
            [TokenType.INTEGER]: this.parseIntegerLiteral,
            [TokenType.BANG_SIGN]: this.parsePrefixExpression,
            [TokenType.MINUS_SIGN]: this.parsePrefixExpression,
            [TokenType.TRUE]: this.parseBoolean,
            [TokenType.FALSE]: this.parseBoolean,
            [TokenType.LEFT_PARENT]: this.parseGroupExpression,
            [TokenType.IF]: this.parseIfExpression,
            [TokenType.FUNCTION]: this.parseFunctionLiteral,
            [TokenType.STRING]: this.parseStringLiteral,
            [TokenType.LEFT_BRACKET]: this.parseArrayLiteral,
            [TokenType.LEFT_BRACE]: this.parseHashLiteral
        }

        // 优先级
        this.priorityMap = {
            [TokenType.EQ]: TokenPriority.EQUALS,
            [TokenType.NOT_EQ]: TokenPriority.EQUALS,
            [TokenType.LT]: TokenPriority.LESSGREATER,
            [TokenType.GT]: TokenPriority.LESSGREATER,
            [TokenType.PLUS_SIGN]: TokenPriority.SUM,
            [TokenType.MINUS_SIGN]: TokenPriority.SUM,
            [TokenType.SLASH]: TokenPriority.PRODUCT,
            [TokenType.ASTERISK]: TokenPriority.PRODUCT,
            [TokenType.LEFT_PARENT]: TokenPriority.CALL,
            [TokenType.LEFT_BRACKET]: TokenPriority.INDEX
        }

        // 中序解析表
        this.infixParseFns = {
            [TokenType.PLUS_SIGN]: this.parseInfixExpression,
            [TokenType.MINUS_SIGN]: this.parseInfixExpression,
            [TokenType.SLASH]: this.parseInfixExpression,
            [TokenType.ASTERISK]: this.parseInfixExpression,
            [TokenType.EQ]: this.parseInfixExpression,
            [TokenType.NOT_EQ]: this.parseInfixExpression,
            [TokenType.LT]: this.parseInfixExpression,
            [TokenType.GT]: this.parseInfixExpression,
            [TokenType.LEFT_PARENT]: this.parseCallExpression,
            [TokenType.LEFT_BRACKET]: this.parseIndexExpression
        }
    }

    nextToken() {
        /*
        一次必须读入两个token,这样我们才了解当前解析代码的意图
        例如假设当前解析的代码是 5; 那么peekToken就对应的就是分号，这样解析器就知道当前解析的代码表示一个整数
        */
        this.curToken = this.peekToken
        this.peekToken = this.tokens[this.tokenPos]
        this.tokenPos++
    }

    parseProgram() {
        while(this.curToken.type !== TokenType.EOF) {
            const stmt = this.parseStatement()
            if (isNotNull(stmt)) {
                this.program.statements.push(stmt)
            }
            this.nextToken()
        }
        return this.program
    }

    parseIndexExpression(that, left) {
        const indexToken = that.curToken
        that.nextToken()
        const index = that.parseExpression(TokenPriority.LOWEST)
        if (!that.expectPeek(TokenType.RIGHT_BRACKET)) {
            return null
        }
        const indexExpr = new IndexExpression(indexToken, left, index)
        console.log("array indexing: ", indexExpr.literal)
        return indexExpr
    }

    parseArrayLiteral(that) {
        const arrToken = that.curToken
        const elements = that.parseExpressionList(TokenType.RIGHT_BRACKET)
        const arrLiteral = new ArrayLiteral(arrToken, elements)
        console.log("parsing array result: ", arrLiteral.literal)
        return arrLiteral
    }

    parseHashLiteral(that) {
        const hashToken = that.curToken
        const keys = []
        const values = []
        while(that.peekTokenIs(TokenType.RIGHT_BRACE) !== true) {
            that.nextToken()
            //先解析expression:expression中左边的算术表达式
            const key = that.parseExpression(TokenPriority.LOWEST)
            //越过中间的冒号
            if (!that.expectPeek(TokenType.COLON)) {
                return null
            }
            that.nextToken()
            //解析冒号右边的表达式
            const value = that.parseExpression(TokenPriority.LOWEST)
            keys.push(key)
            values.push(value)
            //接下来必须跟着逗号或者右括号
            if (!that.peekTokenIs(TokenType.RIGHT_BRACE) && !that.expectPeek(TokenType.COMMA)) {
                return null
            }
        }
         //最后必须以右括号结尾
        if (!that.expectPeek(TokenType.RIGHT_BRACE)) {
            return null
        }
        const obj = new HashLiteral(hashToken, keys, values)
        console.log("parsing map obj: ", obj.literal)
        return obj
    }

    parseExpressionList(end) {
        const list = []
        if (this.peekTokenIs(end)) {
            this.nextToken()
            return list
        }
        this.nextToken()
        list.push(this.parseExpression(TokenPriority.LOWEST))
        while(this.peekTokenIs(TokenType.COMMA)) {
            this.nextToken()
            this.nextToken()
            list.push(this.parseExpression(TokenPriority.LOWEST))
        }
        if (!this.expectPeek(end)) {
            return null
        }
        return list
    }

    parseStatement() {
        switch(this.curToken.type) {
            case TokenType.LET:
                return this.parseLetStatement()
            case TokenType.RETURN:
                return this.parseReturnStatement()
            default:
                return this.parseExpressionStatement()
        }
    }

    // LetStatement ::= LET IDENTIFIER ASSIGN_SIGN INTEGER(EXPRESSION) SEMICOLON
    parseLetStatement() {
        // LET Token
        const letToken = this.curToken
        // expectPeek 会调用nextToken将curToken转换为下一个token
        if (!this.expectPeek(TokenType.IDENTIFIER)) {
            return null
        }
        const identifier = new Identifier(this.curToken)
        if (!this.expectPeek(TokenType.ASSIGN_SIGN)) {
            return null
        }
        this.nextToken()
        const expression = this.parseExpression(TokenPriority.LOWEST)
        if (!this.expectPeek(TokenType.SEMICOLON)) {
            return null
        }
        return new LetStatement(letToken, identifier, expression)
    }

    // ReturnStatement ::= RETURN INTEGER(EXPRESSION) SEMICOLON
    parseReturnStatement() {
        const returnToken = this.curToken
        this.nextToken()
        const expression = this.parseExpression(TokenPriority.LOWEST)
        if (!this.expectPeek(TokenType.SEMICOLON)) {
            return null
        }
        return new ReturnStatement(returnToken, expression)
    }

    parseExpressionStatement() {
        const expr = this.parseExpression(TokenPriority.LOWEST)
        const stmt = new ExpressionStatement(this.curToken, expr)
        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken()
        }
        return stmt
    }


    parseIdentifier({curToken}) {
        return new Identifier(curToken)
    }

    parseIntegerLiteral({curToken}) {
        const value = parseInt(curToken.literal)
        if (isNaN(value)) {
          console.log("could not parse token as integer")
          return null
        }
        return new IntegerLiteral(curToken, value)
    }

    parseStringLiteral({curToken}) {
        return new StringLiteral(curToken)
    }

    parseBoolean(that) {
        return new BooleanExpression(that.curToken, that.curTokenIs(TokenType.TRUE))
    }

    parseGroupExpression(that) {
        that.nextToken()
        let expr = that.parseExpression(TokenPriority.LOWEST)
        if (that.expectPeek(TokenType.RIGHT_PARENT) !== true) {
            return null
        }
        return expr
    }

    parseIfExpression(that) {
        const ifToken = that.curToken
        if(that.expectPeek(TokenType.LEFT_PARENT) !== true) {
            return null
        }
        that.nextToken()
        const condition = that.parseExpression(TokenPriority.LOWEST)
        if (that.expectPeek(TokenType.RIGHT_PARENT) !== true) {
            return null
        }
        if (that.expectPeek(TokenType.LEFT_BRACE) !== true) {
            return null
        }
        const consequence = that.parseBlockStatement(that)
        let alternative = null
        if (that.peekTokenIs(TokenType.ELSE) === true) {
            that.nextToken()
            if (that.expectPeek(TokenType.LEFT_BRACE) !== true) {
                return null
            }
            alternative = that.parseBlockStatement(that)
        }
        return new IfExpression(ifToken, condition, consequence, alternative)
    }

    parseFunctionLiteral(that) {
        const fnToken = that.curToken
        if (that.expectPeek(TokenType.LEFT_PARENT) !== true) {
            return null
        }
        const params = that.parseFunctionParams(that)
        if (that.expectPeek(TokenType.LEFT_BRACE) !== true) {
            return null
        }
        const body = that.parseBlockStatement(that)
        return new FunctionLiteral(fnToken, params, body)
    }

    parseFunctionParams(that) {
        const params = []
        if (that.peekTokenIs(TokenType.RIGHT_PARENT)) {
            that.nextToken()
            return params
        }
        // 越过(
        that.nextToken()
        const idToken = that.curToken
        params.push(new Identifier(idToken))

        while(that.peekTokenIs(TokenType.COMMA)) {
            that.nextToken()
            that.nextToken()
            const idTmpToken = that.curToken
            params.push(new Identifier(idTmpToken))
        }
        if (that.expectPeek(TokenType.RIGHT_PARENT) !== true) {
            return null
        }
        return params
    }

    parseCallExpression(that, fun) {
        const callToken = that.curToken
        const args = that.parseCallArgs(that)
        return new CallExpression(callToken, fun, args)
    }

    parseCallArgs(that) {
        const args = []
        if (that.peekTokenIs(TokenType.RIGHT_PARENT)) {
            that.nextToken()
            return args
        }
        that.nextToken()
        args.push(that.parseExpression(TokenPriority.LOWEST))
        while(that.peekTokenIs(TokenType.COMMA)) {
            that.nextToken()
            that.nextToken()
            args.push(that.parseExpression(TokenPriority.LOWEST))
        }
        if (that.expectPeek(TokenType.RIGHT_PARENT) !== true) {
            return null
        }
        return args
    }


    parseBlockStatement(that) {
        const token = that.curToken
        const statements = []
        that.nextToken()
        while(that.curTokenIs(TokenType.RIGHT_BRACE) !== true) {
            const stmt = that.parseStatement()
            if (isNotNull(stmt)) {
                statements.push(stmt)
            }
            that.nextToken()
        }
        return new BlockStatement(token, statements)
    }

    parsePrefixExpression(that) {
        // 前序符号
        const token = that.curToken
        // 越过前序符合
        that.nextToken()
        // 表达式
        const right = that.parseExpression(TokenPriority.PREFIX)
        return new PrefixExpression(token, token.literal, right)
    }

    parseInfixExpression(that, left) {
        const operatorToken = that.curToken
        const operator = that.curToken.literal
        const priority = that.curPriority()
        that.nextToken()
        const right = that.parseExpression(priority)
        return new InfixExpression(operatorToken, left, operator, right)
    }

    parseExpression(priority) {
        let prefixFn = this.prefixParseFns[this.curToken.type]
        if (isNull(prefixFn)) {
            console.log(`no parsing function found for token ${this.curToken.literal}`)
            return null
        }
        let leftExpr = prefixFn(this)
        while (this.peekTokenIs(TokenType.SEMICOLON) !== true && priority < this.peekPriority()) {
            const infixFn = this.infixParseFns[this.peekToken.type]
            if (isNull(infixFn)) {
                return leftExpr
            }
            this.nextToken()
            leftExpr = infixFn(this, leftExpr)
        }
        return leftExpr
    }

    peekPriority() {
        const p = this.priorityMap[this.peekToken.type]
        if (isNotNull(p)) {
            return p
        }
        return TokenPriority.LOWEST
    }

    curPriority() {
        const p = this.priorityMap[this.curToken.type]
        if (isNotNull(p)) {
            return p
        }
        return TokenPriority.LOWEST
    }

    expectPeek(tokenType) {
        if (this.peekTokenIs(tokenType)) {
            this.nextToken()
            return true
        } else {
            return false
        }
    }
    curTokenIs(tokenType) {
        return this.curToken.type === tokenType
    }
    peekTokenIs(tokenType) {
        return this.peekToken.type === tokenType
    }
}

export default MonkeyParser
